package rest

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	gruntime "runtime"
	"strings"
	"time"

	"gitee.com/youkelike/ziam/zapi/scheme"
	"gitee.com/youkelike/ziam/zsdk2/third_party/forked/gorequest"
	"gitee.com/youkelike/ziam/zutils/runtime"
	tlsutil "gitee.com/youkelike/ziam/zutils/tls"
	"gitee.com/youkelike/ziam/zutils/version"
)

// 第一层配置
type Config struct {
	// 格式 host:port
	Host          string `yaml:"host,omitempty"                    mapstructure:"host,omitempty"`
	APIPath       string `yaml:"apiPath,omitempty" mapstructure:"apiPath,omitempty"`
	ContentConfig `yaml:"contentConfig" mapstructure:"contentConfig,omitempty"`

	// GroupVersion *scheme.GroupVersion `yaml:"groupVersion"                    mapstructure:"groupVersion,omitempty"`

	// Server requires Basic authentication
	// Username string `yaml:"username,omitempty" mapstructure:"username,omitempty"`
	// Password string `yaml:"password,omitempty" mapstructure:"password,omitempty"`

	SecretID  string `yaml:"secretID,omitempty"  mapstructure:"secretID,omitempty"`
	SecretKey string `yaml:"secretKey,omitempty" mapstructure:"secretKey,omitempty"`

	// Server requires Bearer authentication. This client will not attempt to use
	// refresh tokens for an OAuth2 flow.
	// TODO: demonstrate an OAuth2 compatible client.
	BearerToken string `yaml:"bearerToken,omitempty" mapstructure:"bearerToken,omitempty"`

	// Path to a file containing a BearerToken.
	// If set, the contents are periodically read.
	// The last successfully read value takes precedence over BearerToken.
	BearerTokenFile string `yaml:"-" mapstructure:"bearerTokenFile,omitempty"`

	// TLSClientConfig contains settings to enable transport layer security
	tlsutil.TLSClientConfig `yaml:"tlsClient,omitempty" mapstructure:"tlsClient,omitempty"`

	// UserAgent is an optional field that specifies the caller of this request.
	UserAgent string `yaml:"userAgent,omitempty" mapstructure:"userAgent,omitempty"`
	// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
	Timeout       time.Duration `yaml:"timeout,omitempty"                    mapstructure:"timeout,omitempty"`
	MaxRetries    int           `yaml:"maxRetries,omitempty"                mapstructure:"maxRetries,omitempty"`
	RetryInterval time.Duration `yaml:"retryInterval,omitempty"             mapstructure:"retryInterval,omitempty"`
}

type ContentConfig struct {
	// ServiceName        string                   `yaml:"serviceName,omitempty"                    mapstructure:"serviceName,omitempty"`
	AcceptContentTypes string                   `yaml:"acceptContentTypes,omitempty"                    mapstructure:"acceptContentTypes,omitempty"`
	ContentType        string                   `yaml:"contentType,omitempty"                    mapstructure:"contentType,omitempty"`
	GroupVersion       *scheme.GroupVersion     `yaml:"groupVersion,omitempty"                    mapstructure:"groupVersion,omitempty"`
	Negotiator         runtime.ClientNegotiator `yaml:"negotiator,omitempty"                    mapstructure:"negotiator,omitempty"`
}

// type sanitizedConfig Config

// func (c *Config) GoString() string {
// 	return c.String()
// }

// 打印时隐藏部分敏感字段
// func (c *Config) String() string {
// 	if c == nil {
// 		return "<nil>"
// 	}

// 	// nolint: gosimple // no need
// 	// 这里要修改隐藏的字段都是字符串类型，用强制类型转换复制一份后修改即可
// 	cc := sanitizedConfig(*c)

// 	if cc.BearerToken != "" {
// 		cc.BearerToken = "--- REDACTED ---"
// 	}

// 	if cc.SecretKey != "" {
// 		cc.SecretKey = "--- REDACTED ---"
// 	}

// 	return fmt.Sprintf("%#v", cc)
// }

// var (
// 	_ fmt.Stringer   = TLSClientConfig{}
// 	_ fmt.GoStringer = TLSClientConfig{}
// )

// type sanitizedTLSClientConfig TLSClientConfig

// func (c TLSClientConfig) GoString() string {
// 	return c.String()
// }

// // 打印时隐藏部分敏感字段
// func (c TLSClientConfig) String() string {
// 	// nolint: gosimple // no need
// 	cc := sanitizedTLSClientConfig(c)
// 	// Explicitly mark non-empty credential fields as redacted.
// 	if len(cc.CertData) != 0 {
// 		cc.CertData = []byte("--- TRUNCATED ---")
// 	}

// 	if len(cc.KeyData) != 0 {
// 		cc.KeyData = []byte("--- REDACTED ---")
// 	}

// 	return fmt.Sprintf("%#v", cc)
// }

// 先把第一层配置转换成第二层配置，再构建底层客户端，它是对 gorequest 的包装结构
func RESTClientFor(config *Config) (*RESTClient, error) {
	// 先对配置参数做业务层面的校验
	if config.GroupVersion == nil || len(config.GroupVersion.Group) == 0 || len(config.GroupVersion.Version) == 0 {
		return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
	}
	if config.Negotiator == nil {
		return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
	}
	if len(config.ContentType) == 0 {
		config.ContentType = "application/json"
	}

	// url 解析
	// baseURL 是对配置中的 host 解析并去掉 path 和 query 后的内容，
	// versionedAPIPath 是由配置中的 apiPath 和 version 组合而成，
	baseURL, versionedAPIPath, err := defaultServerURLFor(config)
	if err != nil {
		return nil, err
	}

	// TLS 配置
	tlsConfig, err := tlsutil.TLSConfigFor(&config.TLSClientConfig)
	if err != nil {
		return nil, err
	}

	// 构造 gorequest 客户端
	client := gorequest.New().TLSClientConfig(tlsConfig).Timeout(config.Timeout).
		Retry(config.MaxRetries, config.RetryInterval, http.StatusInternalServerError)
	// NOTICE: must set DoNotClearSuperAgent to true, or the client will clean header befor http.Do
	client.DoNotClearSuperAgent = true

	// 这一层配置转换成第二层配置
	clientContent := ClientContentConfig{
		// Username:           config.Username,
		// Password:           config.Password,
		SecretID:           config.SecretID,
		SecretKey:          config.SecretKey,
		BearerToken:        config.BearerToken,
		BearerTokenFile:    config.BearerTokenFile,
		TLSClientConfig:    config.TLSClientConfig,
		AcceptContentTypes: config.AcceptContentTypes,
		ContentType:        config.ContentType,
		GroupVersion:       *config.GroupVersion,
		Negotiator:         config.Negotiator,
	}

	return newRESTClient(baseURL, versionedAPIPath, clientContent, client)
}

func DefaultUserAgent() string {
	return buildUserAgent(
		adjustCommand(os.Args[0]),
		adjustVersion(version.Get().GitVersion),
		gruntime.GOOS,
		gruntime.GOARCH,
		adjustCommit(version.Get().GitCommit))
}
func buildUserAgent(command, version, os, arch, commit string) string {
	return fmt.Sprintf(
		"%s/%s (%s/%s) iam/%s", command, version, os, arch, commit)
}
func adjustCommand(p string) string {
	// Unlikely, but better than returning "".
	if len(p) == 0 {
		return "unknown"
	}

	return filepath.Base(p)
}
func adjustVersion(v string) string {
	if len(v) == 0 {
		return "unknown"
	}

	seg := strings.SplitN(v, "-", 2)

	return seg[0]
}
func adjustCommit(c string) string {
	if len(c) == 0 {
		return "unknown"
	}

	if len(c) > 7 {
		return c[:7]
	}

	return c
}

// 仅仅设置了默认浏览器 UA
func SetIAMDefaults(config *Config) error {
	if len(config.UserAgent) == 0 {
		config.UserAgent = DefaultUserAgent()
	}

	return nil
}
